package jp.dodododo.dao.ext.s2dao.interceptor;

import static jp.dodododo.janerics.GenericsTypeUtil.*;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;

import javax.sql.DataSource;

import jp.dodododo.dao.Dao;
import jp.dodododo.dao.ext.s2dao.annotation.S2DaoExt;
import jp.dodododo.dao.impl.DaoImpl;
import jp.dodododo.dao.util.DaoUtil;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.seasar.dao.DaoMetaData;
import org.seasar.dao.DaoMetaDataFactory;
import org.seasar.dao.SqlCommand;
import org.seasar.dao.impl.AbstractDynamicCommand;
import org.seasar.dao.impl.AbstractSqlCommand;

public class S2DaoExtInterceptor implements MethodInterceptor {

	private DaoMetaDataFactory daoMetaDataFactory;

	private Dao dao;

	public S2DaoExtInterceptor(DataSource dataSource) {
		dao = new DaoImpl(dataSource);
	}

	public S2DaoExtInterceptor(Dao dao) {
		this.dao = dao;
	}

	public Object invoke(MethodInvocation invocation) throws Throwable {
		Method method = invocation.getMethod();
		if (Modifier.isAbstract(method.getModifiers()) == false) {
			return invocation.proceed();
		}
		S2DaoExt annotation = method.getAnnotation(S2DaoExt.class);
		if (annotation == null) {
			return invocation.proceed();
		}

		return select(invocation);
	}

	private Object select(MethodInvocation invocation) {
		Method method = invocation.getMethod();
		Map<String, Object> args = getArgs(invocation);
		Class<?> returnType = method.getReturnType();
		Type genericReturnType = method.getGenericReturnType();
		String sql = getSql(method);

		if (Collection.class.isAssignableFrom(returnType)) {
			Class<?> rowType = getRowType(returnType, genericReturnType);
			if (Map.class.isAssignableFrom(rowType)) {
				return dao.selectMap(sql, args);
			} else {
				return dao.select(sql, args, rowType);
			}
		} else if (Map.class.isAssignableFrom(returnType)) {
			return dao.selectOneMap(sql, args);
		} else {
			return dao.selectOne(sql, returnType);
		}
	}

	private Map<String, Object> getArgs(MethodInvocation invocation) {
		Method method = invocation.getMethod();
		Object[] argumentValues = invocation.getArguments();
		DaoMetaData daoMetaData = daoMetaDataFactory.getDaoMetaData(method.getDeclaringClass());
		SqlCommand sqlCommand = daoMetaData.getSqlCommand(method.getName());
		String[] argNames = ((AbstractDynamicCommand) sqlCommand).getArgNames();
		Object[] args = new Object[argNames.length * 2];
		for (int i = 0; i < argumentValues.length; i++) {
			args[(i * 2)] = argNames[i];
			args[(i * 2) + 1] = argumentValues[i];
		}
		return DaoUtil.args(args);
	}

	private String getSql(Method method) {
		DaoMetaData daoMetaData = daoMetaDataFactory.getDaoMetaData(method.getDeclaringClass());
		SqlCommand sqlCommand = daoMetaData.getSqlCommand(method.getName());
		String sql = ((AbstractSqlCommand) sqlCommand).getSql();
		return sql;
	}

	private Class<?> getRowType(Class<?> returnType, Type genericReturnType) {
		Type elementType = getElementTypeOfCollection(genericReturnType);
		return getRawClass(elementType);
	}

	public void setDaoMetaDataFactory(DaoMetaDataFactory daoMetaDataFactory) {
		this.daoMetaDataFactory = daoMetaDataFactory;
	}

}
